package pl.edu.agh.winmon.widget;

import java.text.Collator;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeEventType;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.data.time.DateRange;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.experimental.chart.swt.ChartComposite;

import pl.edu.agh.winmon.analise.ProccessPropSeriesProvider;
import pl.edu.agh.winmon.hibernate.Process;
import pl.edu.agh.winmon.hibernate.ProcessStatEntry;
import pl.edu.agh.winmon.hibernate.Program;
import pl.edu.agh.winmon.hibernate.Session;

/**
 * This is page which provides information about CPU (static information).
 * 
 * @author kret
 */
public class ProcessInfoWidget extends SessionUpdatableComposite {

	private String[] headers = new String[] { "Program name", "PID",
			"Start time", "CPU usage", "CPU Kernel time", "CPU usage time",
			"CPU total time", "File descriptors", "Total resident memory",
			"Total virtual memory", "Threads" };

	protected TimePickerWidget timePicker;

	protected Table procTable;

	private Session currentSession;

	private ProccessPropSeriesProvider proccessPropSeriesProvider = new ProccessPropSeriesProvider();

	private Combo chartCombo;

	private ChartComposite chartComposite;

	private Group chartGroup;

	protected String[][] data;

	private Map<String[], Process> rowToProcMap = new HashMap<String[], Process>();

	private Set<Process> checked = new HashSet<Process>();

	/**
	 * Constructor.
	 * 
	 * @param parent
	 *            widget's parent
	 */
	public ProcessInfoWidget(Composite parent) {
		super(parent, SWT.NONE);
		setLayout(new FillLayout(SWT.VERTICAL));
		createControls();
	}

	private void createProcList() {

		Group group = new Group(this, SWT.NONE);
		group.setText("Process list");
		group.setLayout(new GridLayout(2, false));

		timePicker = new TimePickerWidget(group);
		timePicker.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent arg0) {
				updateTable();
			}
		});

		procTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION
				| SWT.SINGLE);

		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
		gd.heightHint = 150;
		gd.horizontalSpan = 2;

		procTable.setLayoutData(gd);

		Listener sortListener = new Listener() {
			@SuppressWarnings({ "unchecked", "rawtypes" })
			public void handleEvent(Event e) {
				// determine new sort column and direction
				TableColumn sortColumn = procTable.getSortColumn();
				TableColumn currentColumn = (TableColumn) e.widget;
				int dir = procTable.getSortDirection();
				if (sortColumn == currentColumn) {
					dir = dir == SWT.UP ? SWT.DOWN : SWT.UP;
				} else {
					procTable.setSortColumn(currentColumn);
					dir = SWT.UP;
				}
				// sort the data based on column and direction
				final int index = (Integer) currentColumn.getData();
				final int direction = dir;
				Arrays.sort(data, new Comparator() {
					public int compare(Object arg0, Object arg1) {
						String[] a = (String[]) arg0;
						String[] b = (String[]) arg1;
						try {
							Date date1 = DateFormat.getInstance().parse(
									a[index]);
							Date date2 = DateFormat.getInstance().parse(
									b[index]);
							if (direction == SWT.UP) {
								return date1.compareTo(date2);
							} else {
								return -date1.compareTo(date2);
							}
						} catch (Exception e) {
							try {
								Double d1 = Double.parseDouble(a[index]);
								Double d2 = Double.parseDouble(b[index]);
								if (direction == SWT.UP) {
									return d1.compareTo(d2);
								} else {
									return -d1.compareTo(d2);
								}
							} catch (Exception ex) {
								if (direction == SWT.UP) {
									return Collator.getInstance().compare(
											a[index], b[index]);
								}
								return -Collator.getInstance().compare(
										a[index], b[index]);
							}
						}
					}
				});
				// update data displayed in table
				procTable.setSortDirection(dir);
				procTable.clearAll();
				for (int i = 0; i < data.length; i++) {
					TableItem item = procTable.getItem(i);
					if (item.getChecked()) {
						checked.add((Process) item.getData());
					}

					String[] datum = data[i];
					item.setText(datum);
					item.setData(rowToProcMap.get(datum));
					if (checked.contains(item.getData())) {
						item.setChecked(true);
					}
				}
			}
		};

		procTable.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent arg0) {
				repaintChart();
			}
		});

		for (int i = 0; i < headers.length; i++) {
			TableColumn tableColumn = new TableColumn(procTable, SWT.NONE);
			tableColumn.setText(headers[i]);
			tableColumn.setData(i);
			tableColumn.addListener(SWT.Selection, sortListener);
			tableColumn.pack();
		}

		procTable.setHeaderVisible(true);

		procTable.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDoubleClick(MouseEvent arg0) {
				Shell shell = new Shell(SWT.APPLICATION_MODAL | SWT.CLOSE);
				shell.setLayout(new FillLayout());
				new ProcessDetailDialog(shell, (Process) procTable.getItem(
						new Point(arg0.x, arg0.y)).getData());
				shell.pack();
				shell.open();
			}
		});

		procTable.pack();
	}

	/**
	 * helper methods which allows to create all graphic controls.
	 */
	public void createControls() {
		createProcList();
		// ProcessDetailDialog procDialog = new ProcessDetailDialog(shell);

		chartGroup = new Group(this, SWT.NONE);
		chartGroup.setLayout(new GridLayout(2, false));
		chartGroup.setText("CPU charts");

		Label screenshotDate = new Label(chartGroup, SWT.NONE);
		screenshotDate.setText("Choose chart");

		chartCombo = new Combo(chartGroup, SWT.NONE);
		chartCombo.add(ProccessPropSeriesProvider.CPU_USAGE);
		chartCombo.add(ProccessPropSeriesProvider.FILE_DESCRIPTORS);
		chartCombo.add(ProccessPropSeriesProvider.MEMORY_USAGE);
		chartCombo.add(ProccessPropSeriesProvider.THREADS);
		chartCombo.add(ProccessPropSeriesProvider.VIRTUAL_USAGE);
		chartCombo.select(0);
		chartCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		chartCombo.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent arg0) {
				repaintChart();
			}
		});

	}

	protected void fillUpProcesses(Collection<Process> processes,
			List<String[]> listOfRows) {
		Date startDate = new Date(timePicker.getStartDate());
		Date endDate = new Date(timePicker.getEndDate());

		for (pl.edu.agh.winmon.hibernate.Process process : processes) {
			if (process != null) {
				boolean visible = false;
				Double average[] = new Double[8];
				for (int i = 0; i < 8; i++) {
					average[i] = 0.0;
				}
				int notEmpty[] = new int[8];

				for (ProcessStatEntry procStat : process.getStatEntries()) {
					if (procStat != null) {
						Date date = new Date(procStat.getTurn().getStartTime());
						if (!date.after(endDate) && !date.before(startDate)) {
							visible = true;
							if (procStat.getCpuUsage() >= 0) {
								average[0] += procStat.getCpuUsage() * 100;
								notEmpty[0]++;
							}

							if (procStat.getCpuKernelTime() >= 0) {
								average[1] += procStat.getCpuKernelTime();
								notEmpty[1]++;
							}

							if (procStat.getCpuUserTime() >= 0) {
								average[2] += procStat.getCpuUserTime();
								notEmpty[2]++;
							}

							if (procStat.getCpuTotalTime() >= 0) {
								average[3] += procStat.getCpuTotalTime();
								notEmpty[3]++;
							}

							if (procStat.getFileDescriptors() >= 0) {
								average[4] += procStat.getFileDescriptors();
								notEmpty[4]++;
							}

							if (procStat.getTotalResidentMemory() >= 0) {
								average[5] += procStat.getTotalResidentMemory();
								notEmpty[5]++;
							}

							if (procStat.getTotalVirtualMemory() >= 0) {
								average[6] += procStat.getTotalVirtualMemory();
								notEmpty[6]++;
							}

							if (procStat.getActiveThreads() >= 0) {
								average[7] += procStat.getActiveThreads();
								notEmpty[7]++;
							}

						}
					}
				}

				if (visible) {
					// TableItem tableItem = new TableItem(procTable,
					// SWT.NONE);
					String[] row = new String[11];
					row[0] = process.getProgram().getName();
					row[1] = process.getPid().toString();
					row[2] = DateFormat.getInstance().format(
							process.getStartTime());

					NumberFormat nf = NumberFormat.getInstance();
					nf.setMaximumFractionDigits(2);

					for (int i = 0; i < 8; i++) {
						if (notEmpty[i] > 0) {
							average[i] /= notEmpty[i];
							row[i + 3] = nf.format(average[i]);
						} else {
							row[i + 3] = " ";
						}
					}
					TableItem tableItem = new TableItem(procTable, SWT.NONE);
					tableItem.setText(row);
					tableItem.setData(process);
					rowToProcMap.put(row, process);
					listOfRows.add(row);
				}
			}
		}
	}

	public void updateTable() {

		procTable.clearAll();
		procTable.removeAll();
		List<String[]> listOfRows = new LinkedList<String[]>();

		for (Program program : currentSession.getPrograms()) {
			if (program != null) {
				fillUpProcesses(program.getProcesses(), listOfRows);
			}
		}

		data = listOfRows.toArray(new String[][] {});
	}

	public void updateOnSessionChange(Session session) {
		timePicker.setSession(session);
		currentSession = session;
		updateTable();
		repaintChart();

		procTable.setSortColumn(procTable.getColumn(0));
		procTable.setSortDirection(SWT.UP);
	}

	private void repaintChart() {
		Collection<pl.edu.agh.winmon.hibernate.Process> processes = new LinkedList<pl.edu.agh.winmon.hibernate.Process>();

		for (TableItem tableItem : procTable.getItems()) {
			if (tableItem.getChecked()) {
				processes.add((pl.edu.agh.winmon.hibernate.Process) tableItem
						.getData());
			}
		}

		TimeSeriesCollection xyDataset = proccessPropSeriesProvider
				.provideSeries(currentSession, processes,
						timePicker.getStartDate(), timePicker.getEndDate(),
						chartCombo.getText());

		JFreeChart chart = ChartFactory.createTimeSeriesChart(
				chartCombo.getText() + " in time", // Title
				"Time", // X-Axis label
				chartCombo.getText(), // Y-Axis label
				xyDataset, // Dataset
				true, // Show legend
				false, // Show tooltip
				false);

		if (chartComposite == null) {
			chartComposite = new ChartComposite(chartGroup, SWT.BORDER, chart);
			GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
			gridData.horizontalSpan = 3;

			chartComposite.setLayoutData(gridData);
		} else {
			chartComposite.setChart(chart);
			chartComposite.redraw();
			chartComposite.getParent().layout();
		}
		chartComposite.getChart().addChangeListener(new ChartChangeListener() {

			@Override
			public void chartChanged(ChartChangeEvent arg0) {
				if (arg0.getChart() != null)
					if (arg0.getType().equals(ChartChangeEventType.GENERAL)) {
						timePicker.getStartTimeCombo().setDate(
								((DateRange) arg0.getChart().getXYPlot()
										.getDomainAxis().getRange())
										.getLowerDate());
						timePicker.getEndTimeCombo().setDate(
								((DateRange) arg0.getChart().getXYPlot()
										.getDomainAxis().getRange())
										.getUpperDate());
					}
			}
		});
	}
}
